﻿using System;
using System.Data.Services.Client;
using System.Data.Services.Common;
using System.Linq;

using FluentAssertions;

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

using NUnit.Framework;

namespace SwitchServiceRole.Tests.Models
{
    [TestFixture]
    public class RouteFacts
    {
        [Test]
        public void FindAll()
        {
            var context = new RoutesContext();

            var query = (from r in context.Routes select r).ToList();
            query.Should().NotBeEmpty();
            query.Should().HaveCount(4);
        }

        [TestCase("hwinston@nowhere.com", "saasgateway-dev")]
        [TestCase("hwinston@saasgateway.zz", "saasgateway-dev")]
        [TestCase("jtheisen@saasgateway.zz", "saasgateway-dev")]
        [TestCase("jtheisen@sscompletecare.saasgateway.zz", "saasgateway-dev")]
        [TestCase("jtheisen@sscompletecare.notfound.zz", null)]
        [TestCase("sscompletecare.saasgateway.zz", "saasgateway-dev")]
        [TestCase("@sscompletecare.saasgateway.zz", "saasgateway-dev")]
        [TestCase("", null)]
        [TestCase(null, null)]
        public void FindSpecificAddress(string address, string accountName)
        {
            DetermineAccount(address).Should().Be(accountName);
        }

        private string DetermineAccount(string address)
        {
            if (string.IsNullOrWhiteSpace(address)) return null;

            // now found just the domain part...
            var context = new RoutesContext();

            // does it exist as a participant?
            // does it exist as a domain or wildcard?
            var reversedAddress = ReverseAddress(address);
            var reversedDomain = ReverseDomain(address) + "@";
            var wildedDomain = WildedDomain(address);

            Func<Route, bool> predicate;
            if (reversedAddress == null)
            {
                predicate = r => r.PartitionKey == "rule"
                                 && (r.RowKey == "d:" + reversedDomain ||
                                     r.RowKey == "w:" + wildedDomain);
            }
            else
            {
                predicate = r => r.PartitionKey == "rule"
                                 && (r.RowKey == "a:" + reversedAddress ||
                                     r.RowKey == "d:" + reversedDomain ||
                                     r.RowKey == "w:" + wildedDomain);
            }

            var route = FindFirstOrDefault(context, predicate);

            return route != null ? route.AccountName : null;
        }

        private Route FindFirstOrDefault(RoutesContext context, Func<Route, bool> predicate)
        {
            return (context.Routes.Where(predicate)).Take(1).SingleOrDefault();
        }

        private string WildedDomain(string address)
        {
            var domain = address.Split(new[] { '@' }, 2).Last();
            return string.Join(".", domain.Split('.').Skip(1).Reverse()) + ".*@";
        }

        private string ReverseDomain(string address)
        {
            var reversed = ReverseAddress(address, requireBothParts: false);
            return reversed.Substring(0, reversed.IndexOf("@"));
        }

        //private string CreateSpecialEndCriteria(string text)
        //{
        //    if (string.IsNullOrEmpty(text)) return text;
        //    return text.Substring(0, text.Length - 1) + (char) (text.Last() + 1);
        //}

        private string ReverseAddress(string address, bool requireBothParts = true)
        {
            // step 1 split at the '@'
            var parts = address.Split(new[] { '@' }, 2);
            if (requireBothParts && parts.Length != 2) return null;
            
            var local = parts.First();
            var domain = parts.Last();

            // now split on the '.', reverse the parts and join them again
            return string.Join(".", domain.Split('.').Reverse()) + "@" + local;
        }
    }

    public class RoutesContext : TableServiceContext
    {
        private static readonly CloudStorageAccount StorageAccount
            = CloudStorageAccount.DevelopmentStorageAccount;

        public RoutesContext() 
            : base(StorageAccount.TableEndpoint.AbsoluteUri, StorageAccount.Credentials)
        {
        }

        public DataServiceQuery<Route> Routes
        {
            get { return CreateQuery<Route>("Routes"); }
        }
    }

    [DataServiceKey("PartitionKey", "RowKey")]
    public class Route
    {
        public string Timestamp { get; set; }
        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
        public string AccountName { get; set; }
        public string Alias { get; set; }
        public string PublicKey { get; set; }
    }
}
